
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

#include "relayer.h"

enum
{
  STATE_R = 1, //读态
  STATE_W,     //写态
  STATE_Ex,    //异常态
  STATE_T      //终止态
};

/*
状态机
*/
typedef struct rel_fsm_s
{
  int state;
  int source_fd;
  int dest_fd;
  char buf[BUFSIZE];
  int pos;
  int len;
  char *errstr;
  int64_t count;
} rel_fsm_t;

static struct rel_job_s
{
  int job_state;
  int fd1;
  int fd2;
  struct rel_fsm_s fsm12, fsm21;
  int fd1_save, fd2_save;
};

struct rel_job_s *rel_job[REL_JOBMAX];
static pthread_mutex_t mut_rel_job = PTHREAD_MUTEX_INITIALIZER;
static pthread_once_t init_once = PTHREAD_ONCE_INIT;

/*

*/
void fsm_driver(rel_fsm_t *fsm)
{
  int ret;
  switch (fsm->state)
  {
  case STATE_R:
    fsm->len = read(fsm->source_fd, fsm->buf, BUFSIZE);
    if (fsm->len == 0)
    {
      fsm->state = STATE_T;
    }
    else if (fsm->len < 0)
    {
      if (errno == EAGAIN)
        fsm->state = STATE_R;
      else
      {
        fsm->errstr = "read error!";
        fsm->state = STATE_Ex;
      }
    }
    else
    {
      fsm->pos = 0;
      fsm->state = STATE_W;
    }
    break;
  case STATE_W:
    ret = write(fsm->dest_fd, fsm->buf + fsm->pos, BUFSIZE);
    if (ret < 0)
    {
      if (errno == EAGAIN)
        fsm->state = STATE_W;
      else
      {
        fsm->errstr = "write error!";
        fsm->state = STATE_Ex;
      }
    }
    else
    {
      fsm->pos += ret;
      fsm->len -= ret;
      if (fsm->len == 0)
        fsm->state = STATE_R;
      else
        fsm->state = STATE_W;
    }
    break;
  case STATE_Ex:
    perror(fsm->errstr);
    fsm->state = STATE_T;
    break;
  case STATE_T:
    /*
         do sth
       */
    break;
  default:
    // abort();
    break;
  }
}

/**
状态机，循环读写
*/
void relay(int fd1, int fd2)
{

  rel_fsm_t fsm12, fsm21;
  int fd1_save, fd2_save;
  fd1_save = fcntl(fd1, F_GETFL);
  fcntl(fd1, F_SETFL, fd1_save | O_NONBLOCK);

  fd2_save = fcntl(fd2, F_GETFL);
  fcntl(fd2, F_SETFL, fd2_save | O_NONBLOCK);

  while (fsm12.state != STATE_T || fsm21.state != STATE_T)
  {
    fsm_driver(&fsm12);
    fsm_driver(&fsm21);
  }

  fcntl(fd2, F_SETFL, fd2_save);
  fcntl(fd1, F_SETFL, fd1_save);
}

static void *
thr_relayer(void *p)
{
  while (1)
  {
    pthread_mutex_lock(&mut_rel_job);
    for (int i = 1; i < REL_JOBMAX; i++)
    {
      if (rel_job[i] != NULL)
      {
        if (rel_job[i]->job_state == STATE_RUNNING)
        {
          fsm_driver(&rel_job[i]->fsm12);
          fsm_driver(&rel_job[i]->fsm21);
          if (rel_job[i]->fsm12.state == STATE_T &&
              rel_job[i]->fsm21.state == STATE_T)
          {
            rel_job[i]->job_state = STATE_OVER;
          }
        }
      }
    }
    pthread_mutex_unlock(&mut_rel_job);
  }
}

/*
 *创建一个线程，永远推状态机
 * */
static void
module_load(void)
{
  pthread_t tid_relayer;
  int err = pthread_create(&tid_relayer, NULL, thr_relayer, NULL);
  if (err)
  {
    fprintf(stderr, "pthread_create():%s\n", strerror(err));
    exit(1);
  }
}

int get_free_pos_unlock()
{
  for (int i = 0; i < REL_JOBMAX; i++)
  {
    if (rel_job[i] == NULL)
      return i;
  }
  return -1;
}

int rel_addjob(int fd1, int fd2)
{

  struct rel_job_s *me;

  pthread_once(&init_once, module_load);

  me = malloc(sizeof(*me));
  if (me == NULL)
    return -ENOMEM;
  me->fd1 = fd1;
  me->fd2 = fd2;
  me->job_state = STATE_RUNNING;
  me->fd1_save = fcntl(me->fd1, F_GETFL);
  fcntl(fd1, F_SETFL, me->fd1_save | O_NONBLOCK);
  me->fd2_save = fcntl(me->fd2, F_GETFL);
  fcntl(fd2, F_SETFL, me->fd2_save | O_NONBLOCK);

  me->fsm12.source_fd = me->fd1;
  me->fsm12.dest_fd = me->fd2;
  me->fsm12.state = STATE_R;

  me->fsm21.source_fd = me->fd2;
  me->fsm21.dest_fd = me->fd1;
  me->fsm21.state = STATE_R;

  pthread_mutex_lock(&mut_rel_job);
  int pos = get_free_pos_unlock();
  if (pos < 0)
  {
    pthread_mutex_unlock(&mut_rel_job);
    fcntl(me->fd1, F_SETFL, me->fd1_save);
    fcntl(me->fd2, F_SETFL, me->fd2_save);
    free(me);
    return -ENOSPC;
  }
  rel_job[pos] = me;

  pthread_mutex_unlock(&mut_rel_job);
  return pos;
}

#if 0
int rel_canceljob (int id);


int rel_waitjob (int id, struct rel_stat_s *);

int rel_statjob (int id, struct rel_stat_s *);

#endif
